#!/usr/bin/env python2.7

# Copyright 2016 Nidium Inc. All rights reserved.
# Use of this source code is governed by a MIT license
# that can be found in the LICENSE file.

import sys
import os
import json

from konstructor import Deps
from konstructor import CommandLine
from konstructor import Build
from konstructor import Builder
from konstructor import Platform
from konstructor import Konstruct
from konstructor import Utils
from konstructor import Variables
from konstructor import Tests
from konstructor import PackageManger
from konstructor import Log
from konstructor import ROOT

Gyp = Builder.Gyp

Gyp.setArgs("--depth ./ --include=gyp/config.gypi --include=gyp/common.gypi")
Gyp.set("nidium_product_define", "NIDIUM_PRODUCT_FRONTEND")
Gyp.set("nidium_js_disable_window_global", 0)
Gyp.set("nidium_audio", 1)
Gyp.set("nidium_webgl", 1)

Deps.append(Deps.Konstruct("libnidiumcore", "configure_libnidiumcore"))

DEPEDENCY_REPO = Variables.get("depsRepo")
CONFIGURE_PATH = os.path.dirname(os.path.realpath(__file__))

@Deps.register("portaudio")
def portaudio():
    env = Utils.Env()
    configure = "./configure --disable-shared --enable-static --without-jack --without-asihpi --without-winapi"
    if Konstruct.config("android"):
        location =  Deps.GitRepo("https://github.com/Gundersanne/portaudio_opensles.git", revision="901176c")
        configureArgs = " --host=%s" % (Variables.get("targetTriplet"))
        patch = []
        build = [ "aclocal", "libtoolize --force", "autoconf",
                configure + "--with-opensles %s" % (configureArgs) ]
        env.update(Variables.get("androidEnvClang"))
    else:
        build = [ configure ]
        location = DEPEDENCY_REPO + "/portaudio.tgz"
        patch = [os.path.join(CONFIGURE_PATH, "patch", "portaudio.patch")]
    return {
        "location": location,
        "patchs": patch,
        "build": build + [ "make -j1 lib/libportaudio.la src/common/pa_ringbuffer.o"],
        "outputs": ["lib/.libs/libportaudio.a"],
        "env": env
    }

@Deps.register("zita-resampler")
def zitaresampler():
    env = Utils.Env()

    if Konstruct.config("android"):
        env.update(Variables.get("androidEnvClang"))

    return {
        "location": DEPEDENCY_REPO + "/zita-resampler-1.3.0.tar.bz2",
        "patchs": [os.path.join(CONFIGURE_PATH, "patch", "zita.patch")],
        "chdir": "libs/",
        "build": ["make"],
        "outputs": ["libzita-resampler(.a|.lib)"],
        "env": env
    }

@Deps.register("giflib")
def giflib():
    # skdiff is used during unit-tests and it needs libgif.so.4
    Platform.setEnviron("LD_LIBRARY_PATH=%s:%s/giflib/lib/.libs/" % (Platform.getEnviron("LD_LIBRARY_PATH", ""), os.getcwd()))

    env = Utils.Env()
    configure = "./configure"

    if Konstruct.config("android"):
        env.update(Variables.get("androidEnvClang"))
        # giflib use some old deprecated flags, Workaround that
        # see https://code.google.com/p/android/issues/detail?id=19710
        env.append("CFLAGS", "-include %s/giflib/lib/android.h" % (os.getcwd()))
        configure = "./configure --host=%s" % (Variables.get("targetTriplet"))

    return {
        "location": "http://downloads.sourceforge.net/project/giflib/giflib-4.x/giflib-4.2.3.tar.gz",
        "build": [configure, "make coverity"],
        "outputs": ["lib/.libs/libgif.a"],
        "patchs": [os.path.join(CONFIGURE_PATH, "patch", "giflib_android.patch")],
        "env": env
    }

@Deps.register("skia")
def skia():
    env = Utils.Env()
    build = []
    outputPath = None
    outputs = []
    args = {
        "extra_cflags_cc": [
            "-Wno-array-bounds",
            "-Wno-cast-qual",
            "-Wno-inconsistent-missing-destructor-override",
            "-Wno-return-local-addr",
            "-Wno-unused-function",
            "-Wno-unused-template",
            "-Wno-zero-as-null-pointer-constant",
            "-Wno-expansion-to-defined",
            "-DGR_GL_CUSTOM_SETUP_HEADER=<../../../../../patch/skia_gl_config.h>"
        ],
        "is_official_build": True,
        "cc": Platform.getEnviron("CC"),
        "cxx": Platform.getEnviron("CXX")
    }

    if Platform.system == "Darwin":
        args["extra_cflags_cc"].append("-stdlib=libc++")
        args["extra_ldflags"] = ["-stdlib=libc++"]

    # Add depot_tools to path needed for skia
    Platform.setEnviron("PATH+=" + os.path.join(Deps.getDir(), "depot_tools"))

    # depot_tools and skia needs python2.7 but use "python" as the default
    # interpreter which could be python3.x on some system. Workaround that
    # by using a custom python wrapper that will redirect to the correct python version
    env.set("PATH", os.path.join(ROOT, "tools") + ":" + Platform.getEnviron("PATH", ""))

    if Konstruct.config("android"):
        androidEnv = Variables.get("androidEnvClang")
        args["cc"] = androidEnv.get("CC")
        args["cxx"] = androidEnv.get("CXX")

        args["ndk"] = Variables.get("androidNdkPath")
        args["target_cpu"] = Variables.get("targetArch")

    skiaArgs = ""
    for k, v in args.items():
        skiaArgs += " %s=%s" % (k, json.dumps(v))

    skiaArgs = ""
    for k, v in args.items():
        skiaArgs += " %s=%s" % (k, json.dumps(v))

    return {
        "location": Deps.GitRepo("https://skia.googlesource.com/skia.git", branch="chrome/m58"),
        "build": [
            "python tools/git-sync-deps",
            "bin/gn gen out/Static --args='%s'" % (skiaArgs),
            "sed -i.bak '/simpleperf/d' out/Static/build.ninja",
            "ninja -C out/Static",
        ],
        "patchs": [
            os.path.join(CONFIGURE_PATH, "patch", "skia_gl.patch"),
            os.path.join(CONFIGURE_PATH, "patch", "skia_gr_gl_callback.patch"),
            os.path.join(CONFIGURE_PATH, "patch", "skia_android.patch")
        ],
        "outputs": [
            "out/Static/libskia.a",
            {
                "src": "out/Static/skdiff",
                "dst": os.path.join(ROOT, "tests", "jsunittest", "gr", "skdiff")
            }
        ],
        "env": env
    }

@Deps.register("libzip")
def libzip():
    configureArgs = "--disable-shared --with-zlib=%s/zlib/" % (Deps.getDir())
    env = Utils.Env()

    if Konstruct.config("android"):
        configureArgs = " --host=%s" % (Variables.get("targetTriplet"))
        env.update(Variables.get("androidEnvClang"))

    return {
        "location": "http://www.nih.at/libzip/libzip-1.1.2.tar.gz",
        "build": ["./configure %s" % (configureArgs), "make"],
        "outputs": ["lib/.libs/libzip.a"],
        "env": env
    }

@Deps.register("lss")
def lss():
    return {
        "location": Deps.GitRepo("https://chromium.googlesource.com/linux-syscall-support", revision="348bdf8d32b37c8fb2627df7a0a977d1d640e1fc")
    }

def fixAngleThinArchive():
    # By default ANGLE libs are thin archive, and thus, not relocatable
    # which breaks Konstructor way of dealing with archives. We need to
    # update ninja build file, to create "normal" archive
    if Platform.system == "Linux" or Platform.system == "Darwin":
        Utils.run("sed -i.bak \"s/$ar rcsT/$ar rcs/\" out/Release/build.ninja")

@Deps.register("angle")
def angle():
    env = Utils.Env()
    buildSteps = [
        "python scripts/bootstrap.py",
        "gclient sync",
        fixAngleThinArchive,
        "ninja -C out/Release/ preprocessor translator angle_common"
    ]

    # Disable some errors for clang 3.5 & 3.7
    env.append("CXXFLAGS", " -Wno-c++11-narrowing -Wno-sign-compare -Wno-error -Wno-zero-as-null-pointer-constant")

    # Angle only build with ninja
    env.set("GYP_GENERATORS", "ninja")

    # Angle build script needs python 2.7
    env.set("PATH", os.path.join(ROOT, "tools") + ":" + env.get("PATH"))

    if Platform.wordSize == 32:
        env.append("CXXFLAGS", " -msse -msse2 -msse3")

    if Platform.system == "Darwin":
        outPath = "out/Release/"
    elif Platform.system == "Linux":
        outPath = "out/Release/obj/src/"

    if Konstruct.config("android"):
        env.update(Variables.get("androidEnvClang"))
        # Regenerate gyp files for android
        buildSteps.insert(2, "python build/gyp_angle -Dis_android=1")

    return {
        "location": Deps.GitRepo("https://chromium.googlesource.com/angle/angle", branch="chromium/3208"),
        "build": buildSteps,
        "outputs": [
            os.path.join(outPath, "libpreprocessor.a"),
            os.path.join(outPath, "libtranslator.a"),
            os.path.join(outPath, "libangle_common.a")
        ],
        "env": env
    }

@Deps.register("breakpad")
def breakpad():
    return {
        "location": Deps.GitRepo("https://chromium.googlesource.com/breakpad/breakpad", revision="48b9a40539689743bacbe20df01182b0c367c2c0")
    }

@Deps.register("SDL2")
def SDL2():
    location = "https://www.libsdl.org/release/SDL2-2.0.5.tar.gz"
    if Konstruct.config("android"):
        # On Android SDL2 is built trough gyp (gyp/third-party/SDL2-android.gyp)
        # because SDL2 build script only works with android NDK and the scripts
        # does not match our toolchain
        return {
            "location": location,
            "patchs": [os.path.join(CONFIGURE_PATH, "patch", "SDL2_android.patch")],
        }
    else:
        return {
            "location": location,
            "chdir": "build",
            "build": ["../configure", "make"],
            "outputs": ["build/.libs/libSDL2.a"]
        }

@Deps.register("depot_tools")
def depotTools():
    Deps.Gclient.setExec(os.path.join("depot_tools", "gclient"))
    return {
        "location": Deps.GitRepo("https://chromium.googlesource.com/chromium/tools/depot_tools.git", revision="8bf327c")
    }

@Deps.register("basekit")
def basekit():
    env = Utils.Env()
    if Konstruct.config("android"):
        env.update(Variables.get("androidEnvClang"))
        NdkPath = Variables.get("androidNdkPath")
        NDKAPIVersion = Variables.get("androidNDKAPIVersion")
        arch = "arm"
        env.append("CFLAGS", "-I %s/platforms/android-%d/arch-%s/usr/include/" % (NdkPath, NDKAPIVersion, arch))
    return {
        "location": Deps.GitRepo("https://github.com/stevedekorte/basekit.git", revision="a3e54ba83b85f530dc9442a33d2779240ed96189"),
        "build": ["make"],
        "patchs": [os.path.join(CONFIGURE_PATH, "patch", "basekit.patch")],
        "outputs": ["_build/lib/libbasekit(.a|.lib)"],
        "env": env
    }

@Deps.register("libcoroutine")
def libcoro():
    env = Utils.Env()
    patchs = [os.path.join(CONFIGURE_PATH, "patch", "libcoroutine.patch")]

    if Konstruct.config("android"):
        env.update(Variables.get("androidEnvClang"))

    return {
        "location": Deps.GitRepo("https://github.com/stevedekorte/coroutine.git", revision="b0bf11d8a0ec70bc0f1f5043513d334e1eff15fc"),
        "patchs": patchs,
        "build": ["make"],
        "outputs": [["_build/lib/liblibcoroutine(.a|.lib)", "libcoroutine\\1"]],
        "env": env
    }

@Deps.register("rapidxml")
def rapidxml():
    return {
        "location": "http://downloads.sourceforge.net/project/rapidxml/rapidxml/rapidxml%201.13/rapidxml-1.13.zip",
        "patchs": [  os.path.join(CONFIGURE_PATH, "patch", "rapidxml.patch")],
    }

@Deps.register("ffmpeg")
def ffmpeg():
    env = Utils.Env()
    configureArgs = ''

    if Konstruct.config("android"):
        env.update(Variables.get("androidEnvClang"))
        configureArgs += " --enable-cross-compile"
        configureArgs += " --cross-prefix=%s" % ("arm-linux-androideabi-") #(Variables.get("targetTriplet"))
        configureArgs += " --target-os=linux"
        configureArgs += " --arch=arm"
        configureArgs += " --cpu=cortex-a8"
        configureArgs += " --enable-yasm"
        configureArgs += " --enable-runtime-cpudetect"
        configureArgs += " --enable-pic"
        configureArgs += " --disable-asm"
        configureArgs += " --cc=%s" % (Variables.get("androidEnvClang").get("CC"))
        configureArgs += " --enable-decoder=h264_mediacodec --enable-mediacodec --enable-hwaccel=h264_mediacodec --enable-jni"
        env.set("PATH", os.path.join(ROOT, "tools") + ":" + Platform.getEnviron("PATH", ""))

    return {
        "location": Deps.GitRepo("https://github.com/FFmpeg/FFmpeg.git", branch="release/3.3"),
        "patch": [],
        "build":
        [
            "./configure " + configureArgs + " --disable-shared \
            --disable-vaapi --disable-avdevice --disable-postproc --disable-avfilter --disable-ffserver --disable-lzma --disable-bzlib --disable-vdpau\
            --enable-decoder=ac3,aac,mp3,vorbis,pcm_s16be_planar,pcm_s16le,pcm_s16le_planar,pcm_s24be,pcm_s24daud,pcm_s24le,pcm_s24le_planar,pcm_s32be,pcm_s32le,pcm_s32le_planar,pcm_s8,pcm_s8_planar,pcm_u16be,pcm_u16le,pcm_u24be,pcm_u24le,pcm_u32be,pcm_u32le,pcm_u8,h264,mpeg4,mpeg2video,wmv1,wmv2,wmv3,wmv3_crystalhd,wmv3image,wmalossless,wmapro,wmav1,wmav2,wmavoice,vc1,vc1_crystalhd,vc1image\
            --enable-parser=vorbis,mpegaudio,mpegvideo,mpeg4video,h264,vp8,aac,vc1\
            --enable-demuxer=matroska,mp3,ogg,vorbis,pcm_alaw,pcm_f32be,pcm_f32le,pcm_f64be,pcm_f64le,pcm_mulaw,pcm_s16be,pcm_s16le,pcm_s24be,pcm_s24le,pcm_s32be,pcm_s32le,pcm_s8,pcm_u16be,pcm_u16le,pcm_u24be,pcm_u24le,pcm_u32be,pcm_u32le,pcm_u8,h264,mpegvideo,aac,mov,avi,wav,asf",
            "make V=1"
        ],
        "outputs":
        [
            "libavcodec/libavcodec(.a|.lib)",
            "libswresample/libswresample(.a|.lib)",
            "libavutil/libavutil(.a|.lib)",
            "libavformat/libavformat(.a|.lib)",
            "libswscale/libswscale(.a|.lib)"
        ],
        "env": env
    }

@Deps.register("libnotify")
def libnotify():
    return {
        "location": DEPEDENCY_REPO + "/libnotify-0.7.6.tar.gz",
        "build": ["./configure", "make"],
        "outputs": ["libnotify/.libs/libnotify.a"]
    }


def getNidiumPath():
    nidium = ""
    if Platform.system == "Linux":
        return ("bin/", "./nidium")
    elif Platform.system == "Darwin":
        return ("bin/", "./nidium.app/Contents/MacOS/nidium")
    else:
        raise Exception("TODO : Unsupported platform, cannot find nidum executable")

def addEmbed():
    # Build dir2nvfs
    if not os.path.exists("tools/dir2nvfs"):
        Gyp("gyp/tools.gyp", defines={"nidium_js_disable_window_global": 1,
                                      "nidium_product_define": "DIR2NVFS"}).run("dir2nvfs")

    # Generate the embed file with dir2nvfs
    Gyp("gyp/actions.gyp").run("generate-embed", parallel=False)

    # Now that the embed are packaged
    # we can add add nidium_package_embed flag
    Gyp.set("nidium_package_embed", 1)

@CommandLine.option("--embed", default=False)
def embed(embed):
    if embed:
        @Konstruct.hook("preBuild")
        def preBuildAddEmbed():
            addEmbed()

@CommandLine.option("--auto-tests", default=False)
def autoTestStudio(autoTests):
    if not autoTests:
        return

    tests = []
    nidium = getNidiumPath()

    # Find Konstrutor path, to run Dokumentor
    dokumentor = os.path.dirname(os.path.abspath(sys.modules[Konstruct.__module__].__file__))
    dokumentor = os.path.join(dokumentor, "dokumentor.py")

    Utils.run(dokumentor + " exampletest docs/ > tests/jsautotest/autotests.js")

    Gyp.set("nidium_ui_console", "0")
    tests.append((nidium[1] + " ../tests/jsautotest/autotests.nml", nidium[0]))

    Tests.register(tests)

@CommandLine.option("--unit-tests")
def testStudio(unitTests):
    if not unitTests:
        return

    tests = []
    nidium = getNidiumPath()

    # Log unit-tests to stdout
    Gyp.set("nidium_ui_console", "0")

    tests.append((nidium[1] + " ../tests/jsunittest/unittests.nml --frontend", nidium[0]))

    Tests.register(tests)

@CommandLine.option("--bundle", default=False)
def frontendAndroidBundle(bundle):
    if not bundle:
        return

    Gyp.set("nidium_android_bundle", 1)

@CommandLine.option("--android")
def frontendAndroid(android):
    if not android:
        return

    Gyp.set("nidium_opengles2", 1)

    @Konstruct.hook("postBuild")
    def postBuildAndroid(success):
        import shutil
        if not success:
            return

        cpu = Variables.get("targetCpu")
        libDir = "armeabi"
        if "v7" in cpu:
            libDir = "armeabi-v7a"

        outDir = "src/Frontend/app/android/app/src/main/jniLibs/%s/" % libDir

        Log.info("Copying libnidium_android.so to %s" % outDir)

        Utils.mkdir(outDir)
        shutil.copy("build/out/%s/lib/libnidium_android.so" % Gyp.getConfiguration(), outDir)

        with Utils.Chdir("src/Frontend/app/android/"):
            env = Utils.Env();
            env.set("ANDROID_HOME", os.path.join(ROOT, "third-party", "android-sdk"))

            Log.info("Building android app")
            if Gyp.get("nidium_android_bundle"):
                Utils.run("./gradlew assembleRelease installRelease", env=env)
            else:
                #Utils.run("./gradlew assemble%s" % Gyp.getConfiguration(), env=env)
                Utils.run("./gradlew assembleDebug", env=env)

                Log.info("Installing android app")
                #Utils.run("./gradlew install%s" % Gyp.getConfiguration(), env=env)
                Utils.run("./gradlew installDebug", env=env)

@CommandLine.option("--debug-opengl", default=False)
def glDebug(debug):
    if not debug:
        return

    Gyp.set("nidium_gl_debug", 1);

@CommandLine.option("--debug")
def debug(debug):
    if not debug:
        return

    glDebug(True)

Build.add(Gyp("gyp/all.gyp"));

@Konstruct.hook("start")
def setDependencies():
    Deps.append(
        "depot_tools",
        "angle",
        "giflib",
        "skia",
        "libzip",
        "lss",
        "SDL2",
        "rapidxml",
        #"breakpad",
    )

    if Gyp.get("nidium_audio") == 1:
        Deps.append(
            "zita-resampler",
            "basekit",
            "libcoroutine",
            "portaudio",
            "ffmpeg"
            )

    if not Konstruct.config("android") and Platform.system == "Linux":
        Deps.append("libnotify")

if __name__ == '__main__':
    Log.info("Checking for required software...")
    if not Utils.findExec("yasm"):
        if not PackageManger.detect():
            Utils.exit("Couldn't find a package manager to install \"yasm\" software. Please install it manually and make sure \"yasm\" executable is in your path before building nidium.")

        if not PackageManger.install("yasm", prompt=True):
            Utils.exit("configure script aborted, couldn't install \"yasm\" dependency. Please install it manually and make sure \"yasm\" executable is in your path.")

    Konstruct.start()
